Completed
Push — patch_1-1-4 ( 3f780f...826343 )
by Emanuele
25:17 queued 11:40
created

elk_Mentions.attachAtWho   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 34
rs 9.064
c 0
b 0
f 0
1
/*!
2
 * @name      ElkArte Forum
3
 * @copyright ElkArte Forum contributors
4
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
5
 *
6
 * @version 1.1
7
 */
8
9
/**
10
 * This file contains javascript associated with the atwho function as it
11
 * relates to an sceditor invocation
12
 */
13
var disableDrafts = false;
14
15
(function($, window, document) {
0 ignored issues
show
Unused Code introduced by
The parameter document is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
16
	'use strict';
17
18
	// Editor instance
19
	var editor,
20
		rangeHelper;
21
22
	function elk_Mentions(options) {
23
		// All the passed options and defaults are loaded to the opts object
24
		this.opts = $.extend({}, this.defaults, options);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
25
	}
26
27
	elk_Mentions.prototype.attachAtWho = function(oMentions, $element, oIframeWindow) {
28
		var mentioned = $('#mentioned');
29
30
		// Create / use a container to hold the results
31
		if (mentioned.length === 0)
32
			$('#' + oMentions.opts.editor_id).after(oMentions.opts._mentioned);
33
		else
34
			oMentions.opts._mentioned = mentioned;
35
36
		oMentions.opts.cache.mentions = this.opts._mentioned;
37
38
		$element.atwho({
39
			at: "@",
40
			limit: 8,
41
			maxLen: 25,
42
			displayTpl: "<li data-value='${atwho-at}${name}' data-id='${id}'>${name}</li>",
43
			acceptSpaceBar: true,
44
			callbacks: {
45
				filter: function (query, items, search_key) {
0 ignored issues
show
Unused Code introduced by
The parameter search_key is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter items is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
46
					// Already cached this query, then use it
47
					if (typeof oMentions.opts.cache.names[query] !== 'undefined') {
48
						return oMentions.opts.cache.names[query];
49
					}
50
51
					return [];
52
				},
53
				// Well then lets make a find member suggest call
54
				remoteFilter: function(query, callback) {
55
					// Let be easy-ish on the server, don't go looking until we have at least two characters
56
					if (query.length < 2)
57
						return;
58
59
					// No slamming the server either
60
					var current_call = parseInt(new Date().getTime() / 1000);
61
					if (oMentions.opts._last_call !== 0 && oMentions.opts._last_call + 0.5 > current_call) {
62
						callback(oMentions.opts._names);
63
						return;
64
					}
65
66
					// What we want
67
					var obj = {
68
						"suggest_type": "member",
69
						"search": query.php_urlencode(),
70
						"time": current_call
71
					};
72
73
					// Make the request
74
					suggest(obj, function() {
75
						// Update the time gate
76
						oMentions.opts._last_call = current_call;
77
78
						// Update the cache with the values for reuse in local filter
79
						oMentions.opts.cache.names[query] = oMentions.opts._names;
80
81
						// Update the query cache for use in revalidateMentions
82
						oMentions.opts.cache.queries[oMentions.opts.cache.queries.length] = query;
83
84
						callback(oMentions.opts._names);
85
					});
86
				},
87
				beforeInsert: function(value, $li) {
88
					oMentions.addUID($li.data('id'), $li.data('value'));
89
90
					return value;
91
				},
92
				matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
93
					var _a, _y, match, regexp, space;
94
95
					flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
96
97
					if (should_startWithSpace) {
98
						flag = '(?:^|\\s)' + flag;
99
					}
100
101
					// Allow À - ÿ
102
					_a = decodeURI("%C3%80");
103
					_y = decodeURI("%C3%BF");
104
105
					// Allow first last name entry?
106
					space = acceptSpaceBar ? "\ " : "";
107
108
					// regexp = new RegExp(flag + '([^ <>&"\'=\\\\\n]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi');
109
					regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\\[\\]\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
110
					match = regexp.exec(subtext);
111
112
					if (match) {
113
						return match[2] || match[1];
114
					}
115
					else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
116
						return null;
117
					}
118
				},
119
				highlighter: function(li, query) {
120
					var regexp;
121
122
					if (!query)
123
						return li;
124
125
					// Preg Quote regexp from http://phpjs.org/functions/preg_quote/
126
					query = query.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&');
127
128
					regexp = new RegExp(">\\s*(\\w*)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig');
129
					return li.replace(regexp, function(str, $1, $2, $3) {
130
						return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
131
					});
132
				},
133
				beforeReposition: function (offset) {
134
					// We only need to adjust when in wysiwyg
135
					if (editor.inSourceMode())
136
						return offset;
137
138
					// Lets get the caret position so we can add the mentions box there
139
					var corrected_offset = findAtPosition();
140
141
					offset.top = corrected_offset.top;
142
					offset.left = corrected_offset.left;
143
144
					return offset;
145
				}
146
			}
147
		});
148
149
		// Use atwho selection box show/hide events to prevent autosave from firing
150
		$(oIframeWindow).on("shown.atwho", function(event, offset) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter offset is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
151
			disableDrafts = true;
152
		});
153
154
		$(oIframeWindow).on("hidden.atwho", function(event, offset) {
0 ignored issues
show
Unused Code introduced by
The parameter offset is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
155
			disableDrafts = false;
156
		});
157
158
		/**
159
		 * Makes the ajax call for data, returns to callback function when done.
160
		 *
161
		 * @param obj values to pass to action suggest
162
		 * @param callback function to call when we have completed our call
163
		 */
164
		function suggest(obj, callback)
165
		{
166
			var postString = "jsonString=" + JSON.stringify(obj) + "&" + elk_session_var + "=" + elk_session_id;
0 ignored issues
show
Bug introduced by
The variable elk_session_var seems to be never declared. If this is a global, consider adding a /** global: elk_session_var */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Bug introduced by
The variable elk_session_id seems to be never declared. If this is a global, consider adding a /** global: elk_session_id */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
167
168
			oMentions.opts._names = [];
169
170
			$.ajax({
171
				url: elk_scripturl + "?action=suggest;xml",
0 ignored issues
show
Bug introduced by
The variable elk_scripturl seems to be never declared. If this is a global, consider adding a /** global: elk_scripturl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
172
				type: "post",
173
				data: postString,
174
				dataType: "xml"
175
			})
176
			.done(function(data) {
177
				$(data).find('item').each(function (idx, item) {
178
					if (typeof oMentions.opts._names[oMentions.opts._names.length] === 'undefined')
179
						oMentions.opts._names[oMentions.opts._names.length] = {};
180
181
					oMentions.opts._names[oMentions.opts._names.length - 1] = {
182
						"id": $(item).attr('id'),
183
						"name": $(item).text()
184
					};
185
				});
186
187
				callback();
188
			})
189
			.fail(function(jqXHR, textStatus, errorThrown) {
190
				if ('console' in window) {
191
					window.console.info('Error:', textStatus, errorThrown.name);
192
					window.console.info(jqXHR.responseText);
193
				}
194
195
				callback();
196
			});
197
		}
198
199
		/**
200
		 * Determine the caret position inside of sceditor's iframe
201
		 *
202
		 * What it does:
203
		 * - Caret.js does not seem to return the correct position for (FF & IE) when
204
		 * the iframe has vertically scrolled.
205
		 * - This is an sceditor specific function to return a screen caret position
206
		 * - Called just before At.js adds the mentions dropdown box
207
		 * - Finds the @mentions tag and adds an invisible zero width space before it
208
		 * - Gets the location offset() in the iframe "window" of the added space
209
		 * - Adjusts for the iframe scroll
210
		 * - Adds in the iframe container location offset() to main window
211
		 * - Removes the space, restores the editor range.
212
		 *
213
		 * @returns {{}}
214
		 */
215
		function findAtPosition() {
216
			// Get sceditor's RangeHelper for use
217
			rangeHelper = editor.getRangeHelper();
218
219
			// Save the current state
220
			rangeHelper.saveRange();
221
222
			var start = rangeHelper.getMarker('sceditor-start-marker'),
223
				parent = start.parentNode,
224
				prev = start.previousSibling,
225
				offset = {},
226
				atPos,
227
				placefinder;
228
229
			// Create a placefinder span containing a 'ZERO WIDTH SPACE' Character
230
			placefinder = start.ownerDocument.createElement('span');
231
			$(placefinder).text("200B").addClass('placefinder');
232
233
			// Look back and find the mentions @ tag, so we can insert our span ahead of it
234
			while (prev) {
235
				atPos = (prev.nodeValue || '').lastIndexOf('@');
236
237
				// Found the start of @mention
238
				if (atPos > -1) {
239
					parent.insertBefore(placefinder, prev.splitText(atPos + 1));
240
					break;
241
				}
242
243
				prev = prev.previousSibling;
244
			}
245
246
			// If we were successful in adding the placefinder
247
			if (placefinder.parentNode) {
248
				var $_placefinder = $(placefinder);
249
250
				// offset() returns the top offset inside the total iframe, so we need the vertical scroll
251
				// value to adjust back to main window position
252
				//	wizzy_height = $('#' + oMentions.opts.editor_id).parent().find('iframe').height(),
253
				//	wizzy_window = $('#' + oMentions.opts.editor_id).parent().find('iframe').contents().height(),
254
				var	wizzy_scroll = $('#' + oMentions.opts.editor_id).parent().find('iframe').contents().scrollTop();
255
256
				// Determine its Location in the iframe
257
				offset = $_placefinder.offset();
258
259
				// If we have scrolled, then we also need to account for those offsets
260
				offset.top -= wizzy_scroll;
261
				offset.top += $_placefinder.height();
262
263
				// Remove our placefinder
264
				$_placefinder.remove();
265
			}
266
267
			// Put things back just like we found them
268
			rangeHelper.restoreRange();
269
270
			// Add in the iframe's offset to get the final location.
271
			if (offset) {
272
				var iframeOffset = editor.getContentAreaContainer().offset();
273
274
				// Some fudge for the kids
275
				offset.top += iframeOffset.top + 5;
276
				offset.left += iframeOffset.left + 5;
277
			}
278
279
			return offset;
280
		}
281
	};
282
283
	elk_Mentions.prototype.addUID = function(user_id, name) {
284
		this.opts._mentioned.append($('<input type="hidden" name="uid[]" />').val(user_id).attr('data-name', name));
285
	};
286
287
	/**
288
	 * Private mention vars
289
	 */
290
	elk_Mentions.prototype.defaults = {
291
		_names: [],
292
		_last_call: 0,
293
		_mentioned: $('<div id="mentioned" style="display: none;" />')
294
	};
295
296
	/**
297
	 * Holds all current mention (defaults + passed options)
298
	 */
299
	elk_Mentions.prototype.opts = {};
300
301
	/**
302
	 * Mentioning plugin interface to SCEditor
303
	 *  - Called from the editor as a plugin
304
	 *  - Monitors events so we control the elk_mention
305
	 */
306
	$.sceditor.plugins.mention = function() {
307
		var base = this,
308
			oMentions;
309
310
		base.init = function() {
311
			// Grab this instance for use use in oMentions
312
			editor = this;
313
		};
314
315
		/**
316
		 * Initialize, called when sceditor starts and initializes plugins
317
		 */
318
		base.signalReady = function() {
319
			// Init the mention instance, load in the options
320
			oMentions = new elk_Mentions(this.opts.mentionOptions);
321
322
			var $option_eid = $('#' + oMentions.opts.editor_id);
323
324
			// Adds the selector to the list of known "mentioner"
325
			add_elk_mention(oMentions.opts.editor_id, {isPlugin: true});
326
			oMentions.attachAtWho(oMentions, $option_eid.parent().find('textarea'));
327
328
			// Using wysiwyg, then lets attach atwho to it
329
			var instance = $option_eid.sceditor('instance');
330
			if (!instance.opts.runWithoutWysiwygSupport)
331
			{
332
				// We need to monitor the iframe window and body to text input
333
				var oIframe = $option_eid.parent().find('iframe')[0],
334
					oIframeWindow = oIframe.contentWindow,
335
					oIframeBody = $(oIframe.contentDocument.body);
336
337
					oMentions.attachAtWho(oMentions, oIframeBody, oIframeWindow);
338
			}
339
		};
340
	};
341
})(jQuery, window, document);